#include "codegen.hpp"
#include "hash_map.hpp"
#include "hilang_llvm.hpp"
#include "analyze.hpp"
#include "os.hpp"
#include "config.h"
#include "error.hpp"

#include <stdio.h>

using namespace llvm;
#include "semantic_info.hpp"


CodeGen *codegen_create(Buf *root_source_dir) {
    CodeGen *g = allocate<CodeGen>(1);
    g->fn_table.init(32);
    g->str_table.init(32);
    g->type_table.init(32);
    g->link_table.init(32);
    g->import_table.init(32);
    g->build_type = CodeGenBuildTypeDebug;
    g->root_source_dir = root_source_dir;
    return g;
}

void codegen_set_build_type(CodeGen *g, CodeGenBuildType build_type) {
    g->build_type = build_type;
}

void codegen_set_is_static(CodeGen *g, bool is_static) {
    g->is_static = is_static;
}

void codegen_set_verbose(CodeGen *g, bool verbose) {
    g->verbose = verbose;
}

void codegen_set_strip(CodeGen *g, bool strip) {
    g->strip_debug_symbols = strip;
}

void codegen_set_out_type(CodeGen *g, OutType out_type) {
    g->out_type = out_type;
}

void codegen_set_out_name(CodeGen *g, Buf *out_name) {
    g->root_out_name = out_name;
}

static Type* to_llvm_type(AstNode *type_node) {
    assert(type_node->type == NodeTypeType);
    assert(type_node->codegen_node);
    assert(type_node->codegen_node->data.type_node.entry);

    return type_node->codegen_node->data.type_node.entry->type_ref;
}

static llvm::DIType *to_llvm_debug_type(AstNode *type_node) {
    assert(type_node->type == NodeTypeType);
    assert(type_node->codegen_node);
    assert(type_node->codegen_node->data.type_node.entry);

    return type_node->codegen_node->data.type_node.entry->di_type;
}

static Value* gen_expr(CodeGen *g, AstNode *expr_node);

static void add_debug_source_node(CodeGen *g, AstNode *node) {
    DIScope* scope = g->block_scopes.last();
    g->builder->SetCurrentDebugLocation(DILocation::get(
        scope->getContext(), 
        node->line + 1, node->column + 1, 
        scope));
}

static Value* get_variable_value(CodeGen *g, Buf *name) {
    assert(g->cur_fn->proto_node->type == NodeTypeFnProto);
    int param_count = g->cur_fn->proto_node->data.fn_proto.params.length;
    for (int i = 0; i < param_count; i += 1) {
        AstNode *param_decl_node = g->cur_fn->proto_node->data.fn_proto.params.at(i);
        assert(param_decl_node->type == NodeTypeParamDecl);
        Buf *param_name = &param_decl_node->data.param_decl.name;
        if (buf_eql_buf(name, param_name)) {
            CodeGenNode *codegen_node = g->cur_fn->fn_def_node->codegen_node;
            assert(codegen_node);
            FnDefNode *codegen_fn_def = &codegen_node->data.fn_def_node;
            return codegen_fn_def->params[i];
        }
    }
    hilang_unreachable();
}

static Value* gen_fn_call_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeFnCallExpr);

     Buf *name = hack_get_fn_call_name(g, node->data.fn_call_expr.fn_ref_expr);

    FnTableEntry *fn_table_entry;
    auto entry = g->cur_fn->import_entry->fn_table.maybe_get(name);
    if (entry)
        fn_table_entry = entry->value;
    else
        fn_table_entry = g->fn_table.get(name);
    
    assert(fn_table_entry->proto_node->type == NodeTypeFnProto);
    int expected_param_count = fn_table_entry->proto_node->data.fn_proto.params.length;
    int actual_param_count = node->data.fn_call_expr.params.length;

    assert(expected_param_count == actual_param_count);

    std::vector<Value*> param_values;
    for (int i = 0; i < actual_param_count; i += 1) {
        AstNode *expr_node = node->data.fn_call_expr.params.at(i);
        param_values.push_back(gen_expr(g, expr_node));
    }

    ArrayRef<Value*> params(param_values);
    Value* result = g->builder->CreateCall(fn_table_entry->fn_type, fn_table_entry->fn_value, params); 
    add_debug_source_node(g, node);

    return result;
}

static Value* find_or_create_string(CodeGen *g, Buf *str) {
    auto entry = g->str_table.maybe_get(str);
    if (entry) {
        return entry->value;
    }
    Value* global_value = g->builder->CreateGlobalStringPtr(buf_ptr(str), "", 0, g->mod);
    g->str_table.put(str, global_value);

    return global_value;
}

static Value* gen_arithmetic_bin_op_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    Value* val1 = gen_expr(g, node->data.bin_op_expr.op1);
    Value* val2 = gen_expr(g, node->data.bin_op_expr.op2);

    switch (node->data.bin_op_expr.bin_op) {
        case BinOpTypeBinOr:
            add_debug_source_node(g, node);
            return g->builder->CreateOr(val1, val2, "");
        case BinOpTypeBinXor:
            add_debug_source_node(g, node);
            return g->builder->CreateXor(val1, val2, "");
        case BinOpTypeBinAnd:
            add_debug_source_node(g, node);
            return g->builder->CreateAnd(val1, val2, "");
        case BinOpTypeBitShiftLeft:
            add_debug_source_node(g, node);
            return g->builder->CreateShl(val1, val2, "");
        case BinOpTypeBitShiftRight:
            // TODO implement type system so that we know whether to do
            // logical or arithmetic shifting here.
            // signed -> arithmetic, unsigned -> logical
            add_debug_source_node(g, node);
            return g->builder->CreateLShr(val1, val2, "");
        case BinOpTypeAdd:
            add_debug_source_node(g, node);
            return g->builder->CreateAdd(val1, val2, "");
        case BinOpTypeSub:
            add_debug_source_node(g, node);
            return g->builder->CreateSub(val1, val2, "");
        case BinOpTypeMult:
            // TODO types so we know float vs int
            add_debug_source_node(g, node);
            return g->builder->CreateMul(val1, val2, "");
        case BinOpTypeDiv:
            // TODO types so we know float vs int and signed vs unsigned
            add_debug_source_node(g, node);
            return g->builder->CreateSDiv(val1, val2, "");
        case BinOpTypeMod:
            // TODO types so we know float vs int and signed vs unsigned
            add_debug_source_node(g, node);
            return g->builder->CreateSRem(val1, val2, "");
        case BinOpTypeBoolOr:
        case BinOpTypeBoolAnd:
        case BinOpTypeCmpEq:
        case BinOpTypeCmpNotEq:
        case BinOpTypeCmpLessThan:
        case BinOpTypeCmpGreaterThan:
        case BinOpTypeCmpLessOrEq:
        case BinOpTypeCmpGreaterOrEq:
        case BinOpTypeInvalid:
            hilang_unreachable();
    }
    hilang_unreachable();
}

static Value* gen_prefix_op_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypePrefixOpExpr);
    assert(node->data.prefix_op_expr.primary_expr);

    Value* expr = gen_expr(g, node->data.prefix_op_expr.primary_expr);

    switch (node->data.prefix_op_expr.prefix_op) {
        case PrefixOpNegation:
            add_debug_source_node(g, node);
            return g->builder->CreateNeg(expr, "");
        case PrefixOpBoolNot:
            {
                Value* zero = Constant::getNullValue(expr->getType());
                add_debug_source_node(g, node);
                return g->builder->CreateICmpEQ(expr, zero);
            }
        case PrefixOpBinNot:
            add_debug_source_node(g, node);
            return g->builder->CreateNot(expr);
        case PrefixOpInvalid:
            hilang_unreachable();
    }

    return nullptr;
}

static Value* gen_cast_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeCastExpr);

    Value* expr = gen_expr(g, node->data.cast_expr.prefix_op_expr);

    if (!node->data.cast_expr.type)
        return expr;

    hilang_panic("TODO cast expression");
}

static CmpInst::Predicate cmp_op_to_int_predicate(BinOpType cmp_op, bool is_signed) {
    switch (cmp_op) {
        case BinOpTypeCmpEq:
            return CmpInst::ICMP_EQ;
        case BinOpTypeCmpNotEq:
            return CmpInst::ICMP_NE;
        case BinOpTypeCmpLessThan:
            return is_signed ? CmpInst::ICMP_SLT : CmpInst::ICMP_ULT;
        case BinOpTypeCmpGreaterThan:
            return is_signed ? CmpInst::ICMP_SGT : CmpInst::ICMP_UGT;
        case BinOpTypeCmpLessOrEq:
            return is_signed ? CmpInst::ICMP_SLE : CmpInst::ICMP_ULE;
        case BinOpTypeCmpGreaterOrEq:
            return is_signed ? CmpInst::ICMP_SGE : CmpInst::ICMP_UGE;
        default:
            hilang_unreachable();
    }   
}

static Value* gen_cmp_expr(CodeGen *g, AstNode *node) {
     assert(node->type == NodeTypeBinOpExpr);

    Value* val1 = gen_expr(g, node->data.bin_op_expr.op1);
    Value* val2 = gen_expr(g, node->data.bin_op_expr.op2);

    // TODO implement type system so that we know whether to do signed or unsigned comparison here
    CmpInst::Predicate pred = cmp_op_to_int_predicate(node->data.bin_op_expr.bin_op, true);
    add_debug_source_node(g, node);
    return g->builder->CreateICmp(pred, val1, val2, "");
}

static Value* gen_bool_and_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    Value* val1 = gen_expr(g, node->data.bin_op_expr.op1);

    // block for when val1 == true
    BasicBlock* true_block = BasicBlock::Create(*g->ctxt, "BoolAndTrue");
    // block for when val1 == false (don't even evaluate the second part)
    BasicBlock* false_block = BasicBlock::Create(*g->ctxt, "BoolAndFalse");

    Value* zero = Constant::getNullValue(val1->getType());
    add_debug_source_node(g, node);
    Value* val1_i1 = g->builder->CreateICmp(CmpInst::ICMP_EQ, val1, zero, "");
    g->builder->CreateCondBr(val1_i1, false_block, true_block);

    g->builder->SetInsertPoint(true_block);
    Value* val2 = gen_expr(g, node->data.bin_op_expr.op2);
    add_debug_source_node(g, node);
    Value* val2_i1 = g->builder->CreateICmp(CmpInst::ICMP_EQ, val2, zero, "");

    g->builder->SetInsertPoint(false_block);
    add_debug_source_node(g, node);
    PHINode * phi = g->builder->CreatePHI(Type::getInt1Ty(*g->ctxt), 2, "");
    Value* one_i1 = ConstantInt::getTrue(*g->ctxt);
    phi->addIncoming(one_i1, g->builder->GetInsertBlock());
    phi->addIncoming(val2_i1, true_block);

    return phi;
}

static Value* gen_bool_or_expr(CodeGen *g, AstNode *expr_node) {
    assert(expr_node->type == NodeTypeBinOpExpr);

    Value* val1 = gen_expr(g, expr_node->data.bin_op_expr.op1);

    // block for when val1 == false
    BasicBlock* false_block = BasicBlock::Create(*g->ctxt, "BoolOrFalse");
    // block for when val1 == true (don't even evaluate the second part)
    BasicBlock* true_block = BasicBlock::Create(*g->ctxt, "BoolOrTrue");

    Value* zero = Constant::getNullValue(val1->getType());
    add_debug_source_node(g, expr_node);
    Value* val1_i1 = g->builder->CreateICmpEQ(val1, zero);
    g->builder->CreateCondBr(val1_i1, false_block, true_block);

    g->builder->SetInsertPoint(false_block);
    Value* val2 = gen_expr(g, expr_node->data.bin_op_expr.op2);
    add_debug_source_node(g, expr_node);
    Value* val2_i1 = g->builder->CreateICmpEQ(val2, zero);
    false_block = g->builder->GetInsertBlock();

    g->builder->SetInsertPoint(true_block);
    add_debug_source_node(g, expr_node);
    PHINode* phi = g->builder->CreatePHI(Type::getInt1Ty(*g->ctxt), 2, "");
    Value* one_i1 = ConstantInt::getTrue(*g->ctxt);

    phi->addIncoming(one_i1, g->builder->GetInsertBlock());
    phi->addIncoming(val2_i1, false_block);

    return phi;
}

static Value* gen_bin_op_expr(CodeGen *g, AstNode *node) {
    switch (node->data.bin_op_expr.bin_op) {
        case BinOpTypeInvalid:
            hilang_unreachable();
        case BinOpTypeBoolOr:
            return gen_bool_or_expr(g, node);
        case BinOpTypeBoolAnd:
            return gen_bool_and_expr(g, node);
        case BinOpTypeCmpEq:
        case BinOpTypeCmpNotEq:
        case BinOpTypeCmpLessThan:
        case BinOpTypeCmpGreaterThan:
        case BinOpTypeCmpLessOrEq:
        case BinOpTypeCmpGreaterOrEq:
            return gen_cmp_expr(g, node);
        case BinOpTypeBinOr:
        case BinOpTypeBinXor:
        case BinOpTypeBinAnd:
        case BinOpTypeBitShiftLeft:
        case BinOpTypeBitShiftRight:
        case BinOpTypeAdd:
        case BinOpTypeSub:
        case BinOpTypeMult:
        case BinOpTypeDiv:
        case BinOpTypeMod:
            return gen_arithmetic_bin_op_expr(g, node);
    }
    hilang_unreachable();
}


static Value* gen_return_expr(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeReturnExpr);
    AstNode *param_node = node->data.return_expr.expr;
    if (param_node) {
        Value* value = gen_expr(g, param_node);

        add_debug_source_node(g, node);
        return g->builder->CreateRet(value);
    } else {
        add_debug_source_node(g, node);
        return g->builder->CreateRetVoid();
    }
}

/*
Expression : BoolOrExpression | ReturnExpression
*/
static Value* gen_expr(CodeGen *g, AstNode *node) {
    switch (node->type) {
        case NodeTypeBinOpExpr:
            return gen_bin_op_expr(g, node);
        case NodeTypeReturnExpr:
            return gen_return_expr(g, node);
        case NodeTypeCastExpr:
            return gen_cast_expr(g, node);
        case NodeTypePrefixOpExpr:
            return gen_prefix_op_expr(g, node);
        case NodeTypeFnCallExpr:
            return gen_fn_call_expr(g, node);
        case NodeTypeNumberLiteral:
            {
                Buf *number_str = &node->data.number;
                IntegerType* number_type = Type::getInt32Ty(*(g->ctxt));
                Value* number_val = ConstantInt::get(number_type, 
                    StringRef(buf_ptr(number_str), buf_len(number_str)), 10);
                return number_val;
            }
        case NodeTypeStringLiteral:
            {
                Buf *str = &node->data.string;
                fprintf(stderr, "str = '%s'\n", buf_ptr(str));
                Value* str_val = find_or_create_string(g, str);

                return str_val;
            }
        case NodeTypeSymbol:
            {
                Buf *name = &node->data.symbol;
                return get_variable_value(g, name);
            }
        case NodeTypeRoot:
        case NodeTypeRootExportDecl:
        case NodeTypeFnProto:
        case NodeTypeFnDef:
        case NodeTypeFnDecl:
        case NodeTypeParamDecl:
        case NodeTypeType:
        case NodeTypeBlock:
        case NodeTypeExternBlock:
        case NodeTypeDirective:
        case NodeTypeUse:
            hilang_unreachable();
    }
    hilang_unreachable();
}

static void gen_block(CodeGen *g, ImportTableEntry *import, AstNode *block_node, bool add_implicit_return) {
    assert(block_node->type == NodeTypeBlock);

    DILexicalBlock *di_block = g->dbuilder->createLexicalBlock(g->block_scopes.last(),
            import->di_file, block_node->line + 1, block_node->column + 1);
    g->block_scopes.append(di_block);

    add_debug_source_node(g, block_node);

    for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
        AstNode *statement_node = block_node->data.block.statements.at(i);
        gen_expr(g, statement_node);
    }

    if (add_implicit_return) {
        g->builder->CreateRetVoid();
    }

    g->block_scopes.pop();
}

static DISubroutineType *create_di_function_type(CodeGen *g, AstNodeFnProto *fn_proto, DIFile *di_file) {
    SmallVector<llvm::Metadata *, 8> types;

    DIType *return_type = to_llvm_debug_type(fn_proto->return_type);
    types.push_back(return_type);

    for (int i = 0; i < fn_proto->params.length; i += 1) {
        AstNode *param_node = fn_proto->params.at(i);
        assert(param_node->type == NodeTypeParamDecl);
        llvm::DIType *param_type = to_llvm_debug_type(param_node->data.param_decl.type);
        types.push_back(param_type);
    }

    return g->dbuilder->createSubroutineType(g->dbuilder->getOrCreateTypeArray(types));
}

static void define_primitive_types(CodeGen *g) {
    {
        // if this type is anywhere in the AST, we should never hit codegen.
        TypeTableEntry *entry = allocate<TypeTableEntry>(1);
        buf_init_from_str(&entry->name, "(invalid)");
        g->builtin_types.entry_invalid = entry;
    }
    {
        TypeTableEntry *entry = allocate<TypeTableEntry>(1);
        entry->type_ref = Type::getInt8Ty(*g->ctxt);
        buf_init_from_str(&entry->name, "u8");
        entry->di_type = g->dbuilder->createBasicType(buf_ptr(&entry->name), 8, 8);
        g->type_table.put(&entry->name, entry);
        g->builtin_types.entry_u8 = entry;
    }
    {
        TypeTableEntry *entry = allocate<TypeTableEntry>(1);
        entry->type_ref = Type::getInt32Ty(*g->ctxt);
        buf_init_from_str(&entry->name, "i32");
        entry->di_type = g->dbuilder->createBasicType(buf_ptr(&entry->name), 32, 32);
        g->type_table.put(&entry->name, entry);
        g->builtin_types.entry_i32 = entry;
    }
    {
        TypeTableEntry *entry = allocate<TypeTableEntry>(1);
        entry->type_ref = Type::getVoidTy(*g->ctxt);
        buf_init_from_str(&entry->name, "void");
        entry->di_type = g->dbuilder->createBasicType(buf_ptr(&entry->name), 0, 0);
        g->type_table.put(&entry->name, entry);
        g->builtin_types.entry_void = entry;
    }
}

static void init(CodeGen *g, Buf *source_path) {
    InitializeAllTargets();
    InitializeAllTargetMCs();
    InitializeAllAsmPrinters();
    InitializeAllAsmParsers();
    InitializeNativeTarget();

    g->ctxt = new LLVMContext();
    g->builder = new IRBuilder<>(*(g->ctxt));
    g->mod = new Module("HiLangModule", *(g->ctxt));

    g->dbuilder = new llvm::DIBuilder(*g->mod, true);

    define_primitive_types(g);

    auto native_triple = sys::getDefaultTargetTriple();

    std::string err_msg;
    auto target = TargetRegistry::lookupTarget(native_triple, err_msg);

    auto native_cpu = "generic";
    auto native_features = "";

    TargetOptions opt;
    auto reloc_mode = g->is_static ? Reloc::Static : Reloc::PIC_;
    g->target_machine = target->createTargetMachine(native_triple, native_cpu, native_features, opt, reloc_mode);
    
    g->mod->setDataLayout(g->target_machine->createDataLayout());

    Buf *producer = buf_sprintf("hilang %s", HILANG_VERSION_STRING);
    bool is_optimized = g->build_type == CodeGenBuildTypeRelease;
    const char *flags = "";
    unsigned runtime_version = 0;

    DIFile* difile = g->dbuilder->createFile(buf_ptr(source_path), buf_ptr(g->root_source_dir));
    g->compile_unit = g->dbuilder->createCompileUnit(llvm::dwarf::DW_LANG_C99,
            difile, buf_ptr(producer), is_optimized, flags, runtime_version);

    g->block_scopes.append(g->compile_unit);
}

static void do_code_gen(CodeGen *g) {
    assert(!g->errors.length);
    g->block_scopes.append(g->compile_unit);

    for (int i = 0; i < g->fn_protos.length; i += 1) {
        FnTableEntry *fn_table_entry = g->fn_protos.at(i);

        AstNode *proto_node = fn_table_entry->proto_node;
        assert(proto_node->type == NodeTypeFnProto);
        AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;

        Type* ret_type = to_llvm_type(fn_proto->return_type);
        std::vector<Type*> param_types;
        for (int param_decl_i = 0; param_decl_i < fn_proto->params.length; param_decl_i += 1) {
            AstNode *param_node = fn_proto->params.at(param_decl_i);
            assert(param_node->type == NodeTypeParamDecl);
            AstNode *type_node = param_node->data.param_decl.type;
            param_types.push_back(to_llvm_type(type_node));
        }
        FunctionType* function_type = FunctionType::get(ret_type, param_types, false);
        Function* fn = Function::Create(function_type, Function::ExternalLinkage, buf_ptr(&fn_proto->name), g->mod);
        fn_table_entry->fn_type = function_type;
        fn_table_entry->fn_value = fn;
    }

    // Generate function definitions.
    for (int i = 0; i < g->fn_defs.length; i += 1) {
        FnTableEntry *fn_table_entry = g->fn_defs.at(i);
        ImportTableEntry *import = fn_table_entry->import_entry;
        AstNode *fn_def_node = fn_table_entry->fn_def_node;
        Function* fn = fn_table_entry->fn_value;
        g->cur_fn = fn_table_entry;

        AstNode *proto_node = fn_table_entry->proto_node;
        assert(proto_node->type == NodeTypeFnProto);
        AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;

        // Add debug info.
        unsigned line_number = fn_def_node->line + 1;
        llvm::DIScope *fn_scope = import->di_file;
        DISubprogram* subprogram = g->dbuilder->createFunction(
            fn_scope, buf_ptr(&fn_proto->name), "", import->di_file, line_number,
            create_di_function_type(g, fn_proto, import->di_file), Function::ExternalLinkage, 
            llvm::DINode::FlagZero);

        g->block_scopes.append(subprogram);

        BasicBlock* entry_block = BasicBlock::Create(*(g->ctxt), "entry", fn);
        g->builder->SetInsertPoint(entry_block);

        CodeGenNode *codegen_node = fn_def_node->codegen_node;
        assert(codegen_node);

        FnDefNode *codegen_fn_def = &codegen_node->data.fn_def_node;
        codegen_fn_def->params = allocate<Value*>(fn->arg_size());
        for (size_t i = 0; i < fn->arg_size(); i++) {
            codegen_fn_def->params[i] = fn->getArg(i);
        }
        
        bool add_implicit_return = codegen_node->data.fn_def_node.add_implicit_return;
        gen_block(g, import, fn_def_node->data.fn_def.body, add_implicit_return);

        g->block_scopes.pop();
    }
    assert(!g->errors.length);

    g->mod->print(errs(), nullptr);
    verifyModule(*g->mod);
    g->dbuilder->finalize();
}


static  ImportTableEntry* codegen_add_code(CodeGen *g, Buf *source_path, Buf *source_code) {
    Buf full_path = BUF_INIT;
    os_path_join(g->root_source_dir, source_path, &full_path);

    Buf dirname = BUF_INIT;
    Buf basename = BUF_INIT;
    os_path_split(&full_path, &dirname, &basename);

    if (g->verbose) {
        fprintf(stderr, "\nOriginal Source (%s):\n", buf_ptr(source_path));
        fprintf(stderr, "----------------\n");
        fprintf(stderr, "%s\n", buf_ptr(source_code));

        fprintf(stderr, "\nTokens:\n");
        fprintf(stderr, "---------\n");
    }

    Tokenization tokenization = {0};
    tokenize(source_code, &tokenization);

    if (tokenization.err) {
        ErrorMsg *err = allocate<ErrorMsg>(1);
        err->line_start = tokenization.err_line;
        err->column_start = tokenization.err_column;
        err->line_end = -1;
        err->column_end = -1;
        err->msg = tokenization.err;
        err->path = source_path;
        err->source = source_code;
        err->line_offsets = tokenization.line_offsets;

        print_err_msg(err, g->err_color);
        exit(1);
    }

    if (g->verbose) {
        print_tokens(source_code, tokenization.tokens);

        fprintf(stderr, "\nAST:\n");
        fprintf(stderr, "------\n");
    }

    ImportTableEntry *import_entry = allocate<ImportTableEntry>(1);
    import_entry->source_code = source_code;
    import_entry->line_offsets = tokenization.line_offsets;
    import_entry->path = source_path;
    import_entry->fn_table.init(32);
    import_entry->root = ast_parse(source_code, tokenization.tokens, import_entry, g->err_color);
    assert(import_entry->root);
    if (g->verbose) {
        ast_print(import_entry->root, 0);
    }

    import_entry->di_file = g->dbuilder->createFile(buf_ptr(&basename), buf_ptr(&dirname));
    g->import_table.put(source_path, import_entry);

    assert(import_entry->root->type == NodeTypeRoot);
    for (int decl_i = 0; decl_i < import_entry->root->data.root.top_level_decls.length; decl_i += 1) {
        AstNode *top_level_decl = import_entry->root->data.root.top_level_decls.at(decl_i);
        if (top_level_decl->type != NodeTypeUse)
            continue;

        auto entry = g->import_table.maybe_get(&top_level_decl->data.use.path);
        if (!entry) {
            Buf full_path = BUF_INIT;
            os_path_join(g->root_source_dir, &top_level_decl->data.use.path, &full_path);
            Buf import_code = BUF_INIT;
            os_fetch_file_path(&full_path, &import_code);
            codegen_add_code(g, &top_level_decl->data.use.path, &import_code);
        }
    }

    return import_entry;
}

void codegen_add_root_code(CodeGen *g, Buf *source_path, Buf *source_code) {
    init(g, source_path);

    g->root_import = codegen_add_code(g, source_path, source_code);

    if (g->verbose) {
        fprintf(stderr, "\nSemantic Analysis:\n");
        fprintf(stderr, "--------------------\n");
    }
    semantic_analyze(g);

    if (g->errors.length == 0) {
        if (g->verbose) {
            fprintf(stderr, "OK\n");
        }
    } else {
        for (int i = 0; i < g->errors.length; i += 1) {
            ErrorMsg *err = g->errors.at(i);
            fprintf(stderr, "Error: Line %d, column %d: %s\n",
                    err->line_start + 1, err->column_start + 1,
                    buf_ptr(err->msg));
        }
        exit(1);
    }

    if (g->verbose) {
        fprintf(stderr, "\nCode Generation:\n");
        fprintf(stderr, "------------------\n");
    }

    do_code_gen(g);
}

static Buf *to_c_type(CodeGen *g, AstNode *type_node) {
    assert(type_node->type == NodeTypeType);
    assert(type_node->codegen_node);

    TypeTableEntry *type_entry = type_node->codegen_node->data.type_node.entry;
    assert(type_entry);

    if (type_entry == g->builtin_types.entry_u8) {
        g->c_stdint_used = true;
        return buf_create_from_str("uint8_t");
    } else if (type_entry == g->builtin_types.entry_i32) {
        g->c_stdint_used = true;
        return buf_create_from_str("int32_t");
    } else {
        hilang_panic("TODO");
    }
    hilang_unreachable();
}

static void generate_h_file(CodeGen *g) {
    Buf *h_file_out_path = buf_sprintf("%s.h", buf_ptr(g->root_out_name));
    FILE *out_h = fopen(buf_ptr(h_file_out_path), "wb");
    if (!out_h)
        hilang_panic("unable to open %s: %s", buf_ptr(h_file_out_path), strerror(errno));

    Buf *export_macro = buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name));
    buf_upcase(export_macro);

    Buf *extern_c_macro = buf_sprintf("%s_EXTERN_C", buf_ptr(g->root_out_name));
    buf_upcase(extern_c_macro);

    Buf h_buf = BUF_INIT;
    buf_resize(&h_buf, 0);
    for (int fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) {
        FnTableEntry *fn_table_entry = g->fn_defs.at(fn_def_i);
        AstNode *proto_node = fn_table_entry->proto_node;
        assert(proto_node->type == NodeTypeFnProto);
        AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;

        if (fn_proto->visib_mod != FnProtoVisibModExport)
            continue;

        buf_appendf(&h_buf, "%s %s %s(",
                buf_ptr(export_macro),
                buf_ptr(to_c_type(g, fn_proto->return_type)),
                buf_ptr(&fn_proto->name));

        if (fn_proto->params.length) {
            for (int param_i = 0; param_i < fn_proto->params.length; param_i += 1) {
                AstNode *param_decl_node = fn_proto->params.at(param_i);
                AstNode *param_type = param_decl_node->data.param_decl.type;
                buf_appendf(&h_buf, "%s %s",
                        buf_ptr(to_c_type(g, param_type)),
                        buf_ptr(&param_decl_node->data.param_decl.name));
                if (param_i < fn_proto->params.length - 1)
                    buf_appendf(&h_buf, ", ");
            }
            buf_appendf(&h_buf, ");\n");
        } else {
            buf_appendf(&h_buf, "void);\n");
        }
    }

    Buf *ifdef_dance_name = buf_sprintf("%s_%s_H", buf_ptr(g->root_out_name), buf_ptr(g->root_out_name));
    buf_upcase(ifdef_dance_name);

    fprintf(out_h, "#ifndef %s\n", buf_ptr(ifdef_dance_name));
    fprintf(out_h, "#define %s\n\n", buf_ptr(ifdef_dance_name));

    if (g->c_stdint_used)
        fprintf(out_h, "#include <stdint.h>\n");

    fprintf(out_h, "\n");

    fprintf(out_h, "#ifdef __cplusplus\n");
    fprintf(out_h, "#define %s extern \"C\"\n", buf_ptr(extern_c_macro));
    fprintf(out_h, "#else\n");
    fprintf(out_h, "#define %s\n", buf_ptr(extern_c_macro));
    fprintf(out_h, "#endif\n");
    fprintf(out_h, "\n");
    fprintf(out_h, "#if defined(_WIN32)\n");
    fprintf(out_h, "#define %s %s __declspec(dllimport)\n", buf_ptr(export_macro), buf_ptr(extern_c_macro));
    fprintf(out_h, "#else\n");
    fprintf(out_h, "#define %s %s __attribute__((visibility (\"default\")))\n",
            buf_ptr(export_macro), buf_ptr(extern_c_macro));
    fprintf(out_h, "#endif\n");
    fprintf(out_h, "\n");

    fprintf(out_h, "%s", buf_ptr(&h_buf));

    fprintf(out_h, "\n#endif\n");

    if (fclose(out_h))
        hilang_panic("unable to close h file: %s", strerror(errno));
}

void code_gen_optimize(CodeGen *g) {
}

HiLangList<ErrorMsg*> *codegen_error_messages(CodeGen *g) {
    return &g->errors;
}

void codegen_link(CodeGen *g, const char *out_file) {
    if (g->verbose) {
        fprintf(stderr, "\nLink:\n");
        fprintf(stderr, "-------\n");
    }

    if (!out_file) {
        out_file = buf_ptr(g->root_out_name);
    }

    Buf out_file_o = BUF_INIT;
    buf_init_from_str(&out_file_o, out_file);
    buf_append_str(&out_file_o, ".o");

    std::error_code EC;
    raw_fd_ostream dest(buf_ptr(&out_file_o), EC, sys::fs::OF_None);

    if (EC) {
        errs() << "Could not open file: " << EC.message();
        return;
    }

    legacy::PassManager pass;
    CodeGenFileType file_type = CodeGenFileType::CGFT_ObjectFile;

    if (g->target_machine->addPassesToEmitFile(pass, dest, nullptr, file_type)) {
        hilang_panic("The target machine can't emit a file of this type");
    }

    pass.run(*g->mod);
    dest.flush();

    HiLangList<const char *> args = {0};
    if (g->is_static) {
        args.append("-static");
    }

    if (g->out_type == OutTypeLib) {
        Buf *out_lib_so = buf_sprintf("lib%s.so.%d.%d.%d",
                buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
        Buf *soname = buf_sprintf("lib%s.so.%d", buf_ptr(g->root_out_name), g->version_major);
        args.append("-shared");
        args.append("-Wl,-soname");
        args.append(buf_ptr(soname));
        out_file = buf_ptr(out_lib_so);
    }

    args.append("-o");
    args.append(out_file);
    args.append((const char *)buf_ptr(&out_file_o));
    args.append("-lc");

    auto it = g->link_table.entry_iterator();
    for (;;) {
        auto *entry = it.next();
        if (!entry)
            break;

        Buf *arg = buf_sprintf("-l%s", buf_ptr(entry->key));
        args.append(buf_ptr(arg));
    }

    os_spawn_process("gcc", args, false);

    if (g->out_type == OutTypeLib) {
        generate_h_file(g);
    }

    if (g->verbose) {
        fprintf(stderr, "OK\n");
    }
}

